package t2go

import (
	"fmt"
	"gitee.com/phpdi/cycmd/internal/word"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"text/template"
)

type (
	// T2GoTpl 通过解析数据库表结构，结合tpl文件 生成go代码
	T2GoTpl struct {
		info TableInfo

		cfg *Config
		td  TemplateData
	}

	// Config 配置数据
	Config struct {
		TableName  string //表名
		WorkDir    string //工作目录
		Suffix     string //后缀
		DelimsType int    //0={{, 1=[[
		TimeFiled  []string
		DeletedAt  string
	}

	// Field 字段
	Field struct {
		Pk         bool          //是否为主键字段
		Key        string        //字段
		DBKey      string        //数据库字段
		JsonTag    string        //jsonTag
		GormTag    string        //gormTag
		GoType     string        //Go字段类型
		ProtoType  string        //proto字段类型
		SourceType string        //数据库字段类型
		Comment    *FieldComment //字段数据库注释
		FromType   string        //表单类型用于goadmin
	}

	// FieldComment 字段注释
	FieldComment struct {
		Comment       string //字段数据库注释
		Name          string //字段名，从Comment 解析出来
		Option        bool   //是否有option格式，  例如： 状态,paid=支付,unpaid=未支付
		OptionVar     string //变量名
		OptionContent string //将Comment中 option数据解析成map
		OptionKey     []string
	}

	// TemplateData 模板数据渲染对象
	TemplateData struct {
		EntityName    string     //结构体名称
		EntityTitle   string     //表注释
		TableName     string     //表名
		UrlName       string     //由表名将下杠线转换
		FullFields    []Field    //字段列表
		Fields        []Field    //字段列表，不包含主键,时间字段
		PkField       Field      //主键
		TimeField     []Field    //时间字段
		UniqueKey     [][]string //唯一字段
		HaveUniqueKey bool
	}
)

func NewT2GoTpl(info TableInfo, cfg *Config) *T2GoTpl {
	if cfg.Suffix == "" {
		cfg.Suffix = ".go"
	}
	if len(cfg.TimeFiled) == 0 {
		cfg.TimeFiled = []string{"CreatedAt", "UpdatedAt", "DeletedAt"}
	}
	if cfg.DeletedAt == "" {
		cfg.DeletedAt = "DeletedAt"
	}

	return &T2GoTpl{
		info: info,
		cfg:  cfg,
	}
}

func (tgt *T2GoTpl) Run() (err error) {
	if err = tgt.tableToTemplateData(); err != nil {
		return
	}

	//遍历工作目录对模板进行渲染
	err = filepath.Walk(tgt.cfg.WorkDir, func(path string, info fs.FileInfo, err error) error {
		if !info.IsDir() && strings.HasSuffix(info.Name(), ".tpl") {
			if err := tgt.templateRendering(path, info.Name()); err != nil {
				return err
			}
		}

		return nil
	})

	return
}

func (tgt *T2GoTpl) Clear() (err error) {
	//遍历工作目录对模板进行渲染
	err = filepath.Walk(tgt.cfg.WorkDir, func(path string, info fs.FileInfo, err error) error {
		if !info.IsDir() && strings.HasSuffix(info.Name(), tgt.cfg.Suffix) {
			return os.Remove(path)
		}

		return nil
	})
	return
}

// templateRendering 模板渲染
func (tgt *T2GoTpl) templateRendering(tpl string, fName string) (err error) {
	var (
		tmpl *template.Template
		f    *os.File
		c    []byte
	)

	if c, err = ioutil.ReadFile(tpl); err != nil {
		return
	}

	tmpl = template.New("test")
	switch tgt.cfg.DelimsType {
	case 1:
		tmpl.Delims("[[", "]]")

	}

	//注册模板函数
	RegisterFun(tmpl)

	if tmpl, err = tmpl.Parse(string(c)); err != nil {
		return
	}

	fileName := strings.Replace(tpl, fName, fmt.Sprintf("%s%s", tgt.td.TableName, tgt.cfg.Suffix), 1)
	if f, err = os.Create(fileName); err != nil {
		return
	}
	defer f.Close()

	return tmpl.ExecuteTemplate(f, "test", tgt.td)
}

// tableToTemplateData 将表结构转换为模版数据
func (tgt *T2GoTpl) tableToTemplateData() (err error) {
	var (
		tableInfo Info
	)
	if tableInfo, err = tgt.info.GetTableInfo(tgt.cfg.TableName); err != nil {
		return err
	}

	tgt.td.EntityName = word.UnderscoreToUpperCamelCase(tableInfo.Name)
	tgt.td.EntityTitle = tableInfo.Comment
	tgt.td.TableName = tableInfo.Name
	tgt.td.UrlName = strings.Replace(tableInfo.Name, "_", "-", -1)
	tgt.setFields(tableInfo)
	tgt.td.UniqueKey = tableInfo.UniqueKey
	tgt.td.HaveUniqueKey = len(tableInfo.UniqueKey) > 0

	return

}

func (tgt *T2GoTpl) setFields(tableInfo Info) (res []Field) {
	sort.SliceStable(tableInfo.Columns, func(i, j int) bool {
		if tableInfo.Columns[i].PK && !tableInfo.Columns[j].PK {
			return true
		}
		return false
	})

	for _, v := range tableInfo.Columns {
		fc := &FieldComment{Comment: v.Comment}
		key := word.UnderscoreToUpperCamelCase(v.Field)
		f := Field{
			Pk:         v.PK,
			Key:        key,
			DBKey:      v.Field,
			JsonTag:    fmt.Sprintf(`json:"%s"`, v.Field),
			GormTag:    v.GormTag,
			GoType:     v.GoType,
			ProtoType:  goMapProto[v.GoType],
			FromType:   goMapForm[v.GoType],
			SourceType: v.SourceType,
			Comment:    fc.Parse(tgt.td.EntityName + key),
		}

		if f.Key != tgt.cfg.DeletedAt {
			tgt.td.FullFields = append(tgt.td.FullFields, f)
		}

		if f.Pk {
			tgt.td.PkField = f
			continue
		}

		if Contains(tgt.cfg.TimeFiled, f.Key) {
			tgt.td.TimeField = append(tgt.td.TimeField, f)
			continue
		}

		tgt.td.Fields = append(tgt.td.Fields, f)

	}

	return
}

func (fc *FieldComment) Parse(optionVar string) *FieldComment {
	var content string
	fc.Name = fc.Comment
	if !strings.Contains(fc.Comment, "=") {
		return fc
	}
	arr := strings.Split(fc.Comment, ",")
	if len(arr) == 0 {
		return fc
	}

	fc.Name = arr[0]
	for i := 1; i < len(arr); i++ {
		content += fmt.Sprintf(`"%s",`, strings.Replace(arr[i], "=", `":"`, 1))
		if key := strings.Split(arr[i], "="); len(key) == 2 {
			fc.OptionKey = append(fc.OptionKey, key[0])
		}
	}
	sort.Strings(fc.OptionKey)

	content = strings.TrimRight(content, ",")
	fc.OptionVar = LowerFirstLetter(optionVar)
	fc.OptionContent = fmt.Sprintf(`%s = map[string]string{%s}`, fc.OptionVar, content)
	fc.Option = true

	return fc
}
